/*
 * Some of this class is based on code obtained from the Apache Derby project
 * and modified.  The original Apache header with the licensing information is
 * included below.
 */

/*

   Derby - Class SimpleApp

   Licensed to the Apache Software Foundation (ASF) under one or more
   contributor license agreements.  See the NOTICE file distributed with
   this work for additional information regarding copyright ownership.
   The ASF licenses this file to You under the Apache License, Version 2.0
   (the "License"); you may not use this file except in compliance with
   the License.  You may obtain a copy of the License at

      http://www.apache.org/licenses/LICENSE-2.0

   Unless required by applicable law or agreed to in writing, software
   distributed under the License is distributed on an "AS IS" BASIS,
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   See the License for the specific language governing permissions and
   limitations under the License.

 */

package nz.ac.vuw.ecs.kcassell.anonymous;

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.Properties;

/**
 * This class has no fields. It has some methods that store and retrieve objects
 * from files and some that store and retrieve from databases.
 * @author Keith Cassell
 */
public class AnonymousPersistence2 {

	public void saveSerializableToFile(Object obj, String file) {
		FileOutputStream foStream = null;
		ObjectOutputStream ooStream = null;
		try {
			foStream = new FileOutputStream(file);
			ooStream = new ObjectOutputStream(foStream);
			ooStream.writeObject(obj);
			ooStream.close();
		} catch (IOException e) {
			handleSerializationException("Unable to write to " + file, e);
		}
	}

	public static Object readSerializableFromFile(String file) {
		Object obj = null;
		FileInputStream fiStream = null;
		ObjectInputStream oiStream = null;
		try {
			fiStream = new FileInputStream(file);
			oiStream = new ObjectInputStream(fiStream);
			obj = (Object) oiStream.readObject();
			oiStream.close();
		} catch (IOException e) {
			handleSerializationException("Unable to read from " + file, e);
		} catch (ClassNotFoundException e) {
			handleSerializationException("", e);
		}
		return obj;
	}

	protected static void handleSerializationException(String msg, Exception e) {
		System.err.println(msg + ": " + e);
		e.printStackTrace();
	}

	public Object readFromDB() {
		Object obj = null;
		String url = "org.somewhere.Driver";
		loadDriver(url);
		Connection connection = null;
		Statement statement = null;
		ResultSet resultSet = null;
		try {
			connection = DriverManager.getConnection(url);
			statement = connection.createStatement();
			resultSet = getDatabaseValues(statement);
			// TODO reconstruction via reflection
			resultSet.close();
		} catch (SQLException sqle) {
			handleSQLException(sqle);
		} finally {
			releaseResources(connection, statement, resultSet);
		}
		return obj;
	}

	public void saveToDB(Object obj) {
		String url = "org.apache.derby.jdbc.ClientDriver";
		loadDriver(url);
		Connection connection = null;
		PreparedStatement statement = null;
		try {
			connection = DriverManager.getConnection(url);
			// TODO populate tables using values obtained from reflection
			String sqlString = "INSERT CLASS_TABLE VALUES (?, ?)";
			statement = connection.prepareStatement(sqlString);
			saveDatabaseValues(obj, statement);
		} catch (SQLException sqle) {
			handleSQLException(sqle);
		} finally {
			releaseResources(connection, statement, null);
		}
	}

	public Connection prepareConnection(String url) throws SQLException {
		Properties props = new Properties(); // connection properties
		Connection connection;
		connection = DriverManager.getConnection(url, props);
		connection.setAutoCommit(false);
		return connection;
	}


	/**
	 * By default, the schema APP will be used when no username is provided.
	 * Otherwise, the schema name is the same as the user name. Note that user
	 * authentication is off by default, meaning that any user can connect to
	 * your database using any password. To enable authentication, see the Derby
	 * Developer's Guide.
	 * 
	 * This connection specifies create=true in the connection URL to cause the
	 * database to be created when connecting for the first time. To remove the
	 * database, remove the directory derbyDB (the same as the database name)
	 * and its contents.
	 * 
	 * The directory derbyDB will be created under the directory that the system
	 * property derby.system.home points to, or the current directory (user.dir)
	 * if derby.system.home is not set.
	 */
	// TODO make consistent
	public Connection prepareConnection(String url, String databaseName)
	throws SQLException {
		Properties props = new Properties(); // connection properties
		// providing a user name and password is optional in the embedded
		// and derbyclient frameworks
		// props.put("user", user);
		// props.put("password", password);

		Connection connection;
		connection = DriverManager.getConnection(url, props);
		// connection = DriverManager.getConnection(protocol + databaseName
		// + ";create=true", props);

		System.out.println("Connected to and created database " + databaseName);

		// We want to control transactions manually. Autocommit is on by
		// default in JDBC.
		connection.setAutoCommit(false);
		return connection;
	}

	public void loadDriver(String driver) {
		try {
			Class.forName(driver).newInstance();
		} catch (Exception e) {
			System.err.println("Unable to load the JDBC driver " + driver);
		}
	}

	private ResultSet getDatabaseValues(Statement statement)
			throws SQLException {
		ResultSet resultSet;
		String sqlString = "SELECT * FROM CLASS_TABLE";
		resultSet = statement.executeQuery(sqlString);
		return resultSet;
	}

	private void saveDatabaseValues(Object obj, PreparedStatement statement)
			throws SQLException {
		statement.setString(1, obj.getClass().getSimpleName());
		statement.setString(2, obj.getClass().getPackage().getName());
		statement.executeUpdate();
	}

	public static void handleSQLException(SQLException e) {
		while (e != null) {
			System.err.println("\n----- SQLException: " + e.getMessage());
			System.err.println("  Error Code: " + e.getErrorCode());
			e.printStackTrace(System.err);
			e = e.getNextException();
		}
	}

	public void releaseResources(Connection conn, Statement statement,
			ResultSet result) {
		try {
			if (result != null) result.close();
			if (statement != null) statement.close();
			if (conn != null) conn.close();
		} catch (SQLException sqle) {
			handleSQLException(sqle);
		}
	}

}
